﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using System.IO;
using System.Configuration;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using SimpleInfrastructure.Serialize;
using SimpleInfrastructure.Log;

namespace SimpleInfrastructure.FrameWorks
{
    public static class StackExchangeRedisHelper
    {
        public static string Coonstr = ConfigurationManager.AppSettings["RedisConnectString"] ?? "localhost";
        private static object _locker = new Object();
        private static ConnectionMultiplexer _instance = null;
        private static ILogger _logger = DefaultLogger.Instance;
        
        /// <summary>
        /// 使用一个静态属性来返回已连接的实例，如下列中所示。这样，一旦 ConnectionMultiplexer 断开连接，便可以初始化新的连接实例。
        /// </summary>
        public static ConnectionMultiplexer Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_locker)
                    {
                        if (_instance == null || !_instance.IsConnected)
                        {
                            _instance = ConnectionMultiplexer.Connect(Coonstr);
                        }
                    }
                }
                //注册如下事件
                _instance.ConnectionFailed += MuxerConnectionFailed;
                _instance.ConnectionRestored += MuxerConnectionRestored;
                _instance.ErrorMessage += MuxerErrorMessage;
                _instance.ConfigurationChanged += MuxerConfigurationChanged;
                _instance.HashSlotMoved += MuxerHashSlotMoved;
                _instance.InternalError += MuxerInternalError;
                return _instance;
            }
        }
        static StackExchangeRedisHelper()
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static IDatabase GetDatabase()
        {
            return Instance.GetDatabase();
        }
        
        /// <summary>
        /// 根据key获取缓存对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(string key)
        {
            return Deserialize<T>(GetDatabase().StringGet(key));
        }

        /// <summary>
        /// 根据key获取缓存对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static byte[] GetDirect(string key)
        {
            return GetDatabase().StringGet(key);
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></param>
        public static void Set(string key, object value,TimeSpan? expiry = null)
        {
            GetDatabase().StringSet(key, Serialize(value), expiry);
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></param>
        public static void SetDirect(string key, byte[] value, TimeSpan? expiry = null)
        {
            GetDatabase().StringSet(key, value, expiry);
        }

        /// <summary>
        /// 判断在缓存中是否存在该key的缓存数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool Exists(string key)
        {
            return GetDatabase().KeyExists(key);  //可直接调用
        }

        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public static bool SetExpire(string key, TimeSpan? expiry)
        {
            return GetDatabase().KeyExpire(key, expiry);  //可直接调用
        }

        /// <summary>
        /// 移除指定key的缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool Remove(string key)
        {
            return GetDatabase().KeyDelete(key);
        }

        /// <summary>
        /// 删除前缀所有缓存
        /// </summary>
        /// <param name="prefix"></param>
        public static void KeyDeleteWithPrefix(string prefix)
        {
            var keys = GetKeys(prefix);
            var database = GetDatabase();
            database.KeyDelete(keys.Select(key => (RedisKey)key).ToArray());
        }

        /// <summary>
        /// 获取前缀所有缓存数量
        /// </summary>
        /// <param name="prefix"></param>
        /// <returns></returns>
        public static int KeyCount(string prefix)
        {
            return GetKeys(prefix).Count();
        }

        /// <summary>
        /// 获取前缀所有key
        /// </summary>
        /// <param name="prefix"></param>
        /// <returns></returns>
        public static IEnumerable<string> GetKeys(string prefix)
        {
            if (Instance == null)
            {
                throw new ArgumentException("Connection cannot be null", "connection");
            }

            if (string.IsNullOrWhiteSpace(prefix))
            {
                throw new ArgumentException("prefix cannot be empty", "database");
            }

            var keys = new List<string>();
            var databaseId = Instance.GetDatabase().Database;

            foreach (var endPoint in Instance.GetEndPoints())
            {
                var server = Instance.GetServer(endPoint);
                keys.AddRange(server.Keys(pattern: prefix, database: databaseId).Select(x => x.ToString()));
            }

            return keys.Distinct();
        }

        /// <summary>
        /// 异步设置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static async Task SetAsync(string key, object value)
        {
            await GetDatabase().StringSetAsync(key, Serialize(value));
        }

        /// <summary>
        /// 异步根据key获取缓存对象
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async Task<T> GetAsync<T>(string key)
        {
            var value = await GetDatabase().StringGetAsync(key);
            return Deserialize<T>(value);
        }

        /// <summary>
        /// 实现递增
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static long Increment(string key, long value = 1)
        {
            //三种命令模式
            //Sync,同步模式会直接阻塞调用者，但是显然不会阻塞其他线程。
            //Async,异步模式直接走的是Task模型。
            //Fire - and - Forget,就是发送命令，然后完全不关心最终什么时候完成命令操作。
            //即发即弃：通过配置 CommandFlags 来实现即发即弃功能，在该实例中该方法会立即返回，如果是string则返回null 如果是int则返回0.这个操作将会继续在后台运行，一个典型的用法页面计数器的实现：
            return GetDatabase().StringIncrement(key, value, flags: CommandFlags.FireAndForget);
        }

        /// <summary>
        /// 实现递减
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static long Decrement(string key, long value = 1)
        {
            return GetDatabase().StringDecrement(key, value, flags: CommandFlags.FireAndForget);
        }

        /// <summary>
        /// 序列化对象
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        private static dynamic Serialize(object o)
        {
            if (o == null)
            {
                return null;
            }
            ISerializer serializer = SerializerProvider.GetSerializer(o);
            var objectDataAsStream = serializer.Serialize(o);
            return objectDataAsStream;
        }

        /// <summary>
        /// 反序列化对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        private static T Deserialize<T>(RedisValue stream)
        {
            ISerializer serializer = SerializerProvider.GetSerializer<T>();
            var result = serializer.Deserialize<T>(stream);
            return result;
        }

        /// <summary>
        /// 配置更改时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConfigurationChanged(object sender, EndPointEventArgs e)
        {
            _logger.Log(LogLevel.Information, "Configuration changed: " + e.EndPoint);
        }

        /// <summary>
        /// 发生错误时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerErrorMessage(object sender, RedisErrorEventArgs e)
        {
            _logger.Log(LogLevel.Error, "ErrorMessage: " + e.Message);
        }
        /// <summary>
        /// 重新建立连接之前的错误
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e)
        {
            _logger.Log(LogLevel.Error, e.Exception, "ConnectionRestored: " + e.EndPoint);
        }
        /// <summary>
        /// 连接失败 ， 如果重新连接成功你将不会收到这个通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e)
        {
            _logger.Log(LogLevel.Error, e.Exception, "重新连接：Endpoint failed: " + e.EndPoint + ", " + e.FailureType);
        }
        /// <summary>
        /// 更改集群
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e)
        {
            _logger.Log(LogLevel.Information,
                "HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint);
        }

        /// <summary>
        /// redis类库错误
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerInternalError(object sender, InternalErrorEventArgs e)
        {
            _logger.Log(LogLevel.Error, e.Exception, "InternalError");
        }

        #region  当作消息代理中间件使用 一般使用更专业的消息队列来处理这种业务场景
        /// <summary>
        /// 当作消息代理中间件使用
        /// 消息组建中,重要的概念便是生产者,消费者,消息中间件。
        /// </summary>
        /// <param name="channel"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public static long Publish(string channel, object message)
        {
            ISubscriber sub = Instance.GetSubscriber();
            return sub.Publish(channel, Serialize(message));
        }

        /// <summary>
        /// 在消费者端得到该消息并设置处理方法
        /// </summary>
        /// <param name="channelFrom"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public static void Subscribe<TMesage>(string channelFrom, Action<string, TMesage> handler)
        {
            ISubscriber sub = Instance.GetSubscriber();
            sub.Subscribe(channelFrom, (channel, message) =>
            {
                TMesage msg = Deserialize<TMesage>(message);
                handler(channel, msg);
            });
        }
        #endregion
        
        /// <summary>
        /// GetServer方法会接收一个EndPoint类或者一个唯一标识一台服务器的键值对
        /// 有时候需要为单个服务器指定特定的命令
        /// 使用IServer可以使用所有的shell命令，比如：
        /// DateTime lastSave = server.LastSave();
        /// ClientInfo[] clients = server.ClientList();
        /// 如果报错在连接字符串后加 ,allowAdmin=true;
        /// </summary>
        /// <returns></returns>
        public static IServer GetServer(string host, int port)
        {
            IServer server = Instance.GetServer(host, port);
            return server;
        }

        /// <summary>
        /// 获取全部终结点
        /// </summary>
        /// <returns></returns>
        public static EndPoint[] GetEndPoints()
        {
            EndPoint[] endpoints = Instance.GetEndPoints();
            return endpoints;
        }

    }
}
